/*
* @Author: bill125
* @Date:   2016-06-23 18:33:59
* @Last Modified by:   bill125
* @Last Modified time: 2016-06-26 10:42:42
*/

#include "./draw.h"
#ifdef __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif
#include <iostream>
#include <fstream>
#include <cassert>
#include <cstdlib>
#include <ctime>
#include <algorithm>
#include <cmath>
#include <unistd.h>

Draw Draw::e;

void Draw::reshape(int w, int h)
{  
    glViewport(0, 0, w, h);    
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, w, 0, h, -1.0, 1.0);    
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

void Draw::display() 
{
    Draw::Instance()->Redisplay();
    glutSwapBuffers();
}

void Draw::init()
{
//    Draw::Instance()->SetBallPos(1.5 * Draw::Instance()->Radius(), pi / 2, 0.);
    Draw::Instance()->SetCylinderPos(1.5 * Draw::Instance()->Radius(), 0., 1. * h_ / 2);
    Draw::Instance()->Redisplay();
}

void Draw::mouse(int button, int state, int x, int y)     
{    
    //Regenerate when left-clicked
    if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) {
        Draw::Instance()->SetMousePos(x, y);
        Draw::Instance()->SetLeftButtonDown();
    }

    if (button == GLUT_LEFT_BUTTON && state == GLUT_UP) {
        Draw::Instance()->SetLeftButtonUp();
    }

    if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN) {
        Draw::Instance()->SwitchFrameFlag();
        Draw::Instance()->Redisplay();
    }
}

void Draw::mousemove(int x, int y)
{
    if (Draw::Instance()->LeftButtonDown())
        Draw::Instance()->ProcessMove(x, y);
}

void Draw::keyboard(unsigned char key, int x, int y)
{  
    switch (key) {    
        /*  
         *  Esc:   Terminatethe the programm
         *  n  :   Generate a new blocks
         *  c  :   Clean the screen
         */
        case 'c':
            glClear(GL_COLOR_BUFFER_BIT);    
            glutSwapBuffers();
            break;
        case 'g':
            Draw::Instance()->BeginActionMode();
            break;
        case 'f': 
            Draw::Instance()->StopActionMode();
            break;
        case 'n':
            if (!Draw::Instance()->ActionMode()) break;
            Draw::Instance()->PlaceNext();
            break;
        case 'p':
            if (!Draw::Instance()->ActionMode()) break;
            Draw::Instance()->PlacePrevious();
            break;
        case 27 :
        case 'x':  
            exit(0);
            break;    
    }    
}    

void Draw::Main(int &argc, char ** argv) 
{
    glutInit(&argc, argv);    
    // window    
    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
    glutInitWindowSize(800, 800);
    glutInitWindowPosition(0, 0);

    glutCreateWindow("3D-Placement-Demo");
    
    // Background color
    glClearColor(0.0, 0.0, 0.0, 0.0);    
    glShadeModel(GL_FLAT);
    
    // Configure reacting functions    
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutMouseFunc(mouse);    
    glutMotionFunc(mousemove);
    glutKeyboardFunc(keyboard);
    glutIdleFunc(display);
    init();

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    // Execute
    glutMainLoop();
}

void Draw::LoadCubes(const std::string & File_Name)
{
    std::ifstream fin(File_Name.c_str());
    assert(fin.is_open());
    std::vector<Cube> cubes;
    int size;
    fin >> size;
    for (unsigned i = 0; i < size; ++ i) {
        int l, w, h, x, y, z;
        fin >> l >> w >> h >> x >> y >> z;
//        std::cout << l << w << h << x << y << z << std::endl;
        cubes.push_back(Cube(l, w, h, x, y, z));
    }
    LoadCubes(cubes);
    fin.close();
}

int myrand(int i)
{
    return std::rand()%i;
}

void Draw::LoadCubes(const std::vector<Cube> & cubes)
{
    cubes_.resize(cubes.size());
    for (unsigned i = 0; i < cubes.size(); ++ i)
        cubes_[i] = cubes[i];

    assert(cubes_.size() > 0);
    l_ = cubes_[0].length, w_ = cubes_[0].width, h_ = cubes_[0].height;
    color_.clear();
    dVec n_c;

    for (unsigned i = 0; i < cubes_.size(); ++ i) {
        l_ = std::max(l_, cubes_[i].x + cubes_[i].length);
        w_ = std::max(w_, cubes_[i].y + cubes_[i].width);
        h_ = std::max(h_, cubes_[i].z + cubes_[i].height);

        assert(cubes_[i].x >= 0 && cubes_[i].length > 0);
        assert(cubes_[i].y >= 0 && cubes_[i].width > 0);
        assert(cubes_[i].z >= 0 && cubes_[i].height > 0);

//        n_c.x = 1. * rand() / RAND_MAX * 2 / 3 + 0.15;
//        n_c.y = 1. * rand() / RAND_MAX * 2 / 3 + 0.15;
//        n_c.x = 1. * rand() / RAND_MAX * 2 / 3 + 0.15;
//        color_.push_back(n_c);
    }

    sort(cubes_.begin(), cubes_.end());

    w_point_.x = 1. * l_ / 2.;
    w_point_.y = 1. * w_ / 2.;
    w_point_.z = 1. * h_ / 2.;

    frame_.x = frame_.y = frame_.z = -0.05;
    frame_.length = l_ + 0.1, frame_.width = w_ + 0.1, frame_.height = h_ + 0.1;

    std::srand(time(NULL));
//    std::cout << "Now Time:" << time(NULL) << std::endl;;
//    std::cout << rand()%1000 << std::endl;
    std::ifstream fin("color.sav");
    color_.clear();
//    color_.resize(100);
    for (unsigned i = 0; i < 100; ++ i) {
        dVec n_c;
        fin >> n_c.x >> n_c.y >> n_c.z;
//        std::cout << n_c.x << " " << n_c.y << " " << n_c.z << std::endl;
        color_.push_back(n_c);
    }
    fin.close();
    std::random_shuffle(color_.begin(), color_.end(), myrand);
}

void Draw::DrawAllCubes() 
{
    for (unsigned i = 0; i < cubes_.size(); ++ i)
        DrawCube(cubes_[i], color_[i % color_.size()]);
}

void Draw::DrawDisplayCubes() 
{
    for (unsigned i = 0; i < display_num_; ++ i)
        DrawCube(cubes_[i], color_[i % color_.size()]);
}


void Draw::FlyCube(const dCube & c, dVec color)
{
//    std::cout << "Fly" << std::endl;
    dCube tc = c;
    double fly_distance = pow(l_, 0.333) * 2;

    tc.x += fly_distance;
    const double FlyInterval = 0.3; // sec
    const int LoopTimes = 30;
    int FirstStageTimes = 15, SecondStageTimes = 15;
    clock_t Start = clock();
    dVec speed = dVec(-1. * fly_distance * 17 / 20 / FirstStageTimes, 0., 0.);
    dVec asped = dVec(-1  * fly_distance * 6 / 20 / SecondStageTimes / SecondStageTimes, 0., 0.);
    for (int i = 0; i < LoopTimes; ++ i) {
        if (i == FirstStageTimes)
            speed = dVec(-1. * fly_distance * 6 / 20 / SecondStageTimes, 0., 0.);
        if (i > FirstStageTimes)
            speed.x -= asped.x;
        tc.x += speed.x;
        Redisplay(false);
        DrawCube(tc, color);
        if (frame_flag_)
            DrawFrame();
        glutSwapBuffers();
//        std::cout << "Flying " << i << " " << tc.x << std::endl;
        clock_t now = clock();
        usleep(1000. * (i + 1) * FlyInterval / LoopTimes - 1000. * (now - Start) / CLOCKS_PER_SEC);
    }
}


void Draw::DrawCube(const dCube & c, dVec color)
{
//    std::cout << color.x << std::endl;
    const static int quad_id[][4] = {
        0, 1, 2, 3,
        4, 5, 6, 7,
        1, 2, 6, 5,
        2, 3, 7, 6,
        3, 7, 4, 0,
        0, 1, 5, 4,
    };
    double l = c.length, w = c.width, h = c.height;
    double x = c.x, y = c.y, z = c.z;
    std::vector<double> n_x(8, 0.), n_y(8, 0.), n_z(8, 0.);

    n_x[0] = x,     n_y[0] = y,     n_z[0] = z;
    n_x[1] = x,     n_y[1] = y + w, n_z[1] = z;
    n_x[2] = x + l, n_y[2] = y + w, n_z[2] = z;
    n_x[3] = x + l, n_y[3] = y,     n_z[3] = z;
    n_x[4] = x,     n_y[4] = y,     n_z[4] = z + h;
    n_x[5] = x,     n_y[5] = y + w, n_z[5] = z + h;
    n_x[6] = x + l, n_y[6] = y + w, n_z[6] = z + h;
    n_x[7] = x + l, n_y[7] = y,     n_z[7] = z + h;

    // set color 
    glEnable(GL_BLEND);
    glColor4f(color.x, color.y, color.z, 1.0);

    for (unsigned i = 0; i < 6; ++ i) {
        std::vector<double> vertex(3, 0.);
        glBegin(GL_QUADS);
        for (unsigned j = 0; j < 4; ++ j) {
            int k = quad_id[i][j];
            glVertex3d(n_x[k], n_y[k], n_z[k]);
        }
        glEnd();
    }
    glDisable(GL_BLEND);

    glColor3f(1.0, 1.0, 1.0);
    glBegin(GL_LINES);
    for (unsigned i = 0; i < 6; ++ i) {
        for (unsigned j = 0; j < 4; ++ j) {
            int k1 = quad_id[i][(j + 1) % 4];
            int k2 = quad_id[i][j];
            glVertex3d(n_x[k1], n_y[k1], n_z[k1]);
            glVertex3d(n_x[k2], n_y[k2], n_z[k2]);
        }
    }
    glEnd();
}

void Draw::DrawFrame()
{
//    std::cout << color.x << std::endl;
    const static int quad_id[][4] = {
        0, 1, 2, 3,
        4, 5, 6, 7,
        1, 2, 6, 5,
        2, 3, 7, 6,
        3, 7, 4, 0,
        0, 1, 5, 4,
    };
    double l = frame_.length, w = frame_.width, h = frame_.height;
    double x = frame_.x, y = frame_.y, z = frame_.z;
    std::vector<double> n_x(8, 0.), n_y(8, 0.), n_z(8, 0.);

    n_x[0] = x,     n_y[0] = y,     n_z[0] = z;
    n_x[1] = x,     n_y[1] = y + w, n_z[1] = z;
    n_x[2] = x + l, n_y[2] = y + w, n_z[2] = z;
    n_x[3] = x + l, n_y[3] = y,     n_z[3] = z;
    n_x[4] = x,     n_y[4] = y,     n_z[4] = z + h;
    n_x[5] = x,     n_y[5] = y + w, n_z[5] = z + h;
    n_x[6] = x + l, n_y[6] = y + w, n_z[6] = z + h;
    n_x[7] = x + l, n_y[7] = y,     n_z[7] = z + h;

    // set color 
    glEnable(GL_BLEND);
    glColor4f(1.0, 1.0, 1.0, 0.3);
    glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);

    for (unsigned i = 0; i < 6; ++ i) {
        std::vector<double> vertex(3, 0.);
        glBegin(GL_QUADS);
        for (unsigned j = 0; j < 4; ++ j) {
            int k = quad_id[i][j];
            glVertex3d(n_x[k], n_y[k], n_z[k]);
        }
        glEnd();
    }
    glDisable(GL_BLEND); 

    glColor3f(1.0, 1.0, 1.0);
    glBegin(GL_LINES);
    for (unsigned i = 0; i < 6; ++ i) {
        for (unsigned j = 0; j < 4; ++ j) {
            int k1 = quad_id[i][(j + 1) % 4];
            int k2 = quad_id[i][j];
            glVertex3d(n_x[k1], n_y[k1], n_z[k1]);
            glVertex3d(n_x[k2], n_y[k2], n_z[k2]);
        }
    }
    glEnd();
}

void Draw::CalcViewpoint()
{
    cur_view_point_.x = w_point_.x + R_ * cos(theta_ + delta_x_ * pi / 180. / 1.5);
    cur_view_point_.y = w_point_.y + R_ * sin(theta_ + delta_x_ * pi / 180. / 1.5);
    cur_view_point_.z = z_;
}

void Draw::RefreshSight()
{
    if (!action_mode_)
        Draw::Instance()->DrawAllCubes();
    else
        Draw::Instance()->DrawDisplayCubes();

    if (frame_flag_)
        Draw::Instance()->DrawFrame();
}

void Draw::Redisplay(bool refresh/* = true */)
{
    CalcViewpoint();
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity(); 
    gluPerspective(45, 1, 10, 10000); 
    glMatrixMode(GL_MODELVIEW); 
    glLoadIdentity(); 
    gluLookAt(cur_view_point_.x, cur_view_point_.y, cur_view_point_.z,
              w_point_.x, w_point_.y , w_point_.z, 
              0, 0, 1);
    if (refresh)
        RefreshSight();
    else
        if (!action_mode_)
            Draw::Instance()->DrawAllCubes();
        else
            Draw::Instance()->DrawDisplayCubes();
}

void Draw::BeginActionMode()
{
    display_num_ = 0;
    action_mode_ = true;
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    Redisplay();
    glutSwapBuffers();
}

void Draw::StopActionMode()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    action_mode_ = false;
    Redisplay();
}

bool Draw::PlaceNext()
{
    if (display_num_ >= cubes_.size())
        return false;

//    std::cout << "cube " << display_num_ << " placed" << std::endl;
    FlyCube(cubes_[display_num_], color_[display_num_ % color_.size()]);
    glutSwapBuffers();

    ++display_num_;

    return true;
}

bool Draw::PlacePrevious()
{
    if (display_num_ == 0)
        return false;

//    std::cout << "cube " << display_num_ << " backtraced" << std::endl;

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    --display_num_;

    Redisplay();
    glutSwapBuffers();

    return true;
}

void Draw::ProcessMove(int x, int y) 
{
    delta_x_ += oldmx_ - x;
    z_ += 0.13 * (y - oldmy_); 
    if (z_ > 2.5 * h_) z_ = 2.5 * h_;
    if (z_ < -1.5 * h_) z_ = -1.5 * h_;
    oldmx_ = x,oldmy_ = y; 
}

void Draw::SetMousePos(int x, int y)
{
    oldmx_ = x, oldmy_ = y;
}

